package com.ohuang.eventbus;



import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

class SubscriberMethodFinder {
    private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new HashMap<>();
    private static final int BRIDGE = 0x40;
    private static final int SYNTHETIC = 0x1000;
    private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;

    List<SubscriberMethod> findSubscriberMethods(Class<?> clazz) {
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(clazz);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
        subscriberMethods = findUsingInfo(clazz);
        if (subscriberMethods.isEmpty()) {
            throw new RuntimeException("Subscriber " + clazz.getName()
                    + " and its super classes have no public methods with the @Subscribe annotation");
        } else {
            METHOD_CACHE.put(clazz, subscriberMethods);
            return subscriberMethods;
        }

    }


    static void clearCaches() {
        METHOD_CACHE.clear();
    }
    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = new FindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findUsingReflectionInSingleClass(findState);
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);

    }


    private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
        List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
        findState.recycle();
        return subscriberMethods;
    }

    private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }

        for (Method method : methods) {

            int modifiers = method.getModifiers();
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {

                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                        }else {
                            throw new RuntimeException("在"+findState.clazz.getName()
                                    +"类中 "+
                                    "@Subscribe注解"+method.getName()+"方法的"+eventType.getName()+"()类型参数已被注册 请使用其他类型参数"
                                    );
                        }
                    }
                } else {
                    if (method.getAnnotation(Subscribe.class) != null) {
                        throw new RuntimeException(findState.clazz.getName()
                                +"类中 @Subscribe注解"
                                +method.getName()+"()方法的参数个数只能是一个");
                    }
                }
            }
        }
    }



    static class FindState {
        final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
        final Map<Class, Object> anyMethodByEventType = new HashMap<>();
        Class<?> subscriberClass;
        Class<?> clazz;
        boolean skipSuperClasses;

        void initForSubscriber(Class<?> subscriberClass) {
            this.subscriberClass = clazz = subscriberClass;
            skipSuperClasses = false;

        }

        void recycle() {
            subscriberMethods.clear();
            subscriberClass = null;
            clazz = null;
            skipSuperClasses = false;
        }

        boolean checkAdd(Method method, Class<?> eventType) {
            // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
            // Usually a subscriber doesn't have methods listening to the same event type.
            Object existing = anyMethodByEventType.put(eventType, method);
            return existing == null;
        }

        void moveToSuperclass() {
            if (skipSuperClasses) {
                clazz = null;
            } else {
                clazz = clazz.getSuperclass();
                String clazzName = clazz.getName();
                /** Skip system classes, this just degrades performance. */
                if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) {
                    clazz = null;
                }
            }
        }

    }
}
